//TODO:original version had ignore-error feature
#include "ccuberenderer.h"
#include "../desktoprenderer/cdesktoprenderer.h"
#include "croot.h"
#include "cclient.h"

#include <X11/Xregion.h>

#include <buola/gl/ctexture.h>
#include <buola/gui/x11/cx11display.h>
#include <buola/image/cimagegc.h>

#include "shaders/sTransformVertex.h"
#include "shaders/sColorFragment.h"
#include "shaders/sTextureFragment.h"

namespace buola { namespace bwm {

gl::CProgram CCubeRenderer::mProgram,CCubeRenderer::mTexProgram;
gl::CShader CCubeRenderer::mVertexShader,CCubeRenderer::mColorShader,
            CCubeRenderer::mTextureShader;
gl::CUniform CCubeRenderer::mUniformMV,CCubeRenderer::mUniformMVP;

///////////////////////////////////////////////////////////////////
//                                                               //
//                   creation and initialization                 //
//                                                               //
///////////////////////////////////////////////////////////////////

CCubeRenderer::CCubeRenderer()
    :   mHAngle(0),
        mVAngle(0),
        mXTranslate(0),
        mYTranslate(0),
        mZoom(1),
        mHAngleRec(nullptr),
        mVAngleRec(nullptr),
        mXTranslateRec(nullptr),
        mYTranslateRec(nullptr),
        mZoomRec(nullptr)
{
}

CCubeRenderer::~CCubeRenderer()
{
}

void CCubeRenderer::Enable()
{
    if(!mProgram)
    {
        mVertexShader.Create(gl::CShader::VERTEX,shaders::sTransformVertex);
        mVertexShader.Compile();

        mColorShader.Create(gl::CShader::FRAGMENT,shaders::sColorFragment);
        mColorShader.Compile();

        mTextureShader.Create(gl::CShader::FRAGMENT,shaders::sTextureFragment);
        mTextureShader.Compile();

        mProgram.Create();
        mProgram.Attach(mVertexShader);
        mProgram.Attach(mColorShader);

        mProgram.BindFragmentData(0,"oFragColor");
        mProgram.BindAttribLocation(0,"iVertex");
        mProgram.BindAttribLocation(1,"iColor");
        mProgram.BindAttribLocation(2,"iNormal");

        mProgram.Link();
        
        mTexProgram.Create();
        mTexProgram.Attach(mVertexShader);
        mTexProgram.Attach(mTextureShader);

        mTexProgram.BindFragmentData(0,"oFragColor");
        mTexProgram.BindAttribLocation(0,"iVertex");
        mTexProgram.BindAttribLocation(1,"iColor");
        mTexProgram.BindAttribLocation(2,"iNormal");
        mTexProgram.BindAttribLocation(3,"iTexCoord");

        mTexProgram.Link();
        mUniformMV=mTexProgram.GetUniform("uMVMatrix");
        mUniformMVP=mTexProgram.GetUniform("uMVPMatrix");
        msg_info() << "compiled and linked\n";
        
        for(int i=0;i<4;i++)
            mVBO[i].Create(16384);
        
        glGenVertexArrays(1,&mVertexArray);
    }
}

void CCubeRenderer::Reshape(Window pOverlay,const CSize_d &pSize)
{
    XMoveResizeWindow(gui::x11::get_default_x11_display()->GetDisplay(),pOverlay,0,0,
                    (int)pSize.x,(int)pSize.y);

    mSize=pSize;
    mRasterPos={0,0};
    
    glViewport(0,0,(int)mSize.x,(int)mSize.y);

    mFOV=50.0;
    mCamDist=1.0/(2.0*tan(mFOV*M_PI/360.0));

    mProjectionMatrix=mat::perspective(deg2rad(mFOV),1.0f,0.1f,100.0f);
    mModelView=mat::nIdentity;
}


void CCubeRenderer::AnimateDesktopSwitch(int pFrom,int pTo)
{
    double lInc=gRoot->mDesktops.mAngle[pTo]-gRoot->mDesktops.mAngle[pFrom];
    if(lInc<-180)
        lInc+=360;
    else if(lInc>180)
        lInc-=360;
    StartHRotate(lInc,chrono::milliseconds(300),true);
}

void CCubeRenderer::DragStep(gui::EDragState pType,const CPoint_d &pDelta)
{
    if(pType==gui::DRAG_CTRLALTLB)
    {
        StartHRotate(-0.3*pDelta.x,chrono::milliseconds(50),true);
        StartVRotate(0.3*pDelta.y,chrono::milliseconds(50),true);
    }
    else if(pType==gui::DRAG_CTRLALTRB)
    {
        StartXYTranslate(0.001*pDelta.x,-0.001*pDelta.y,chrono::milliseconds(50),true,true);
    }
}

void CCubeRenderer::DragFinish(gui::EDragState pType)
{
    StartRecovery();
}

void CCubeRenderer::StartHRotate(double pDest,const chrono::microseconds &pTime,
                                                        bool pInc)
{
    if(pInc)
    {
        double lCurrent;

        if(mHAngleRec)
        {
            lCurrent=mHAngleRec->x1;
            delete mHAngleRec;
            mHAngleRec=nullptr;
        }
        else
        {
            lCurrent=mHAngle;
        }

        pDest+=lCurrent;
    }

    if(pTime==chrono::microseconds(0))
    {
        mHAngle=pDest;
        gRoot->DamageAll();
    }
    else
    {
        mHAngleRec=new SShapeRecovery(gRoot->GetTime().mCurrent,pTime,mHAngle,pDest);
    }
}

void CCubeRenderer::StartVRotate(double pDest,const chrono::microseconds &pTime,bool pInc)
{
    if(pInc)
    {
        double lCurrent;

        if(mVAngleRec)
        {
            lCurrent=mVAngleRec->x1;
            delete mVAngleRec;
            mVAngleRec=nullptr;
        }
        else
        {
            lCurrent=mVAngle;
        }

        pDest+=lCurrent;
    }

    if(pDest<-90.0) pDest=-90.0;
    if(pDest>90.0) pDest=90.0;

    if(pTime==chrono::microseconds(0))
    {
        mVAngle=pDest;
        gRoot->DamageAll();
    }
    else
    {
        mVAngleRec=new SShapeRecovery(gRoot->GetTime().mCurrent,pTime,
                                    mVAngle,pDest);
    }
}

void CCubeRenderer::StartXYTranslate(double pXDest,double pYDest,
                    const chrono::microseconds &pTime,bool pInc,bool pZoomMult)
{
    if(pZoomMult)
    {
        pXDest/=mZoom;
        pYDest/=mZoom;
    }

    if(pInc)
    {
        double lXCurrent,lYCurrent;

        if(mXTranslateRec)
        {
            lXCurrent=mXTranslateRec->x1;
            delete mXTranslateRec;
            mXTranslateRec=nullptr;
        }
        else
        {
            lXCurrent=mXTranslate;
        }

        if(mYTranslateRec)
        {
            lYCurrent=mYTranslateRec->x1;
            delete mYTranslateRec;
            mYTranslateRec=nullptr;
        }
        else
        {
            lYCurrent=mYTranslate;
        }

        pXDest+=lXCurrent;
        pYDest+=lYCurrent;
    }

    if(pXDest!=mXTranslate)
        mXTranslateRec=new SShapeRecovery(gRoot->GetTime().mCurrent,pTime,
                                        mXTranslate,pXDest);
    if(pYDest!=mYTranslate)
        mYTranslateRec=new SShapeRecovery(gRoot->GetTime().mCurrent,pTime,
                                        mYTranslate,pYDest);
}

void CCubeRenderer::StartZoom(double pDest,const chrono::microseconds &pTime,bool pMult)
{
    if(pMult)
    {
        double lCurrent;

        if(mZoomRec)
        {
            lCurrent=mZoomRec->x1;
            delete mZoomRec;
        }
        else
        {
            lCurrent=mZoom;
        }

        pDest*=lCurrent;
    }

    mZoomRec=new SShapeRecovery(gRoot->GetTime().mCurrent,pTime,
                                        mZoom,pDest);
}

void CCubeRenderer::StartRecovery()
{
    //find nearest zero
    double lTurns=round(mHAngle/gRoot->mDesktops.mAngleCovered);
    gRoot->SwitchToDesktop(((int)lTurns)%gRoot->mDesktops.mCount,false);

    StartHRotate(lTurns*gRoot->mDesktops.mAngleCovered,chrono::milliseconds(300),false);
    StartVRotate(0,chrono::milliseconds(300),false);
    StartXYTranslate(0,0,chrono::milliseconds(300),false,false);
}

void CCubeRenderer::DoRecovery(const CMonotonicTime &pTime)
{
    if(mHAngleRec)
    {
        mHAngle=mHAngleRec->GetPos(pTime);
        gRoot->DamageAll();
        if(mHAngleRec->IsFinished(pTime))
        {
            delete mHAngleRec;
            mHAngleRec=nullptr;
        }
    }
    if(mVAngleRec)
    {
        mVAngle=mVAngleRec->GetPos(pTime);
        gRoot->DamageAll();
        if(mVAngleRec->IsFinished(pTime))
        {
            delete mVAngleRec;
            mVAngleRec=nullptr;
        }
    }
    if(mXTranslateRec)
    {
        mXTranslate=mXTranslateRec->GetPos(pTime);
        gRoot->DamageAll();
        if(mXTranslateRec->IsFinished(pTime))
        {
            delete mXTranslateRec;
            mXTranslateRec=nullptr;
        }
    }
    if(mYTranslateRec)
    {
        mYTranslate=mYTranslateRec->GetPos(pTime);
        gRoot->DamageAll();
        if(mYTranslateRec->IsFinished(pTime))
        {
            delete mYTranslateRec;
            mYTranslateRec=nullptr;
        }
    }
    if(mZoomRec)
    {
        mZoom=mZoomRec->GetPos(pTime);
        gRoot->DamageAll();
        if(mZoomRec->IsFinished(pTime))
        {
            delete mZoomRec;
            mZoomRec=nullptr;
        }
    }
}

void CCubeRenderer::Transform(int pDesktop,bool pSpace)
{
    mModelView=mModelView*mat::translation_3d(mat::CVec3d(mXTranslate,mYTranslate,-mCamDist/mZoom));
    mModelView=mModelView*mat::translation_3d(mat::CVec3d(0.0,0.0,-gRoot->mDesktops.mDistance));
    mModelView=mModelView*mat::rotation_x_3d(deg2rad(mVAngle));
    mModelView=mModelView*mat::rotation_y_3d(deg2rad(-mHAngle+gRoot->mDesktops.mAngle[pDesktop]));
    mModelView=mModelView*mat::translation_3d(mat::CVec3d(0.0,0.0,gRoot->mDesktops.mDistance));

    //transform to desktop space
    if(pSpace)
    {
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(-0.5,-0.5,0.0));
        mModelView=mModelView*mat::scaling_3d(mat::CVec3d(1.0/mSize.x,-1.0/mSize.y,1.0/mSize.y));
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(0.0f,-mSize.y,0.0f));
    }
}

void CCubeRenderer::Render()
{
    msg_info() << "rendering!!!!\n";
    static double lFirstTime=0.0;
    static double lSecondTime=0.0;
    
    if(!(gRoot->mFlags&COMP_DAMAGE))
    {
        gRoot->WaitForVSync();
        return;
    }

    gRoot->DamageAll();

    gRoot->mRegions.mPainted.clear();

    glFinish();

    if(gRoot->mUseDepthTest)
    {
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_ALWAYS);
        glClearDepth(0.0);
        glClear(GL_DEPTH_BUFFER_BIT);
    }
    else
    {
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_BLEND);
    }
    
    
    gl::clear_color(img::CColor(0.7,0.3,0.5));
    glClear(GL_COLOR_BUFFER_BIT);

    mProgram.Use();
    glBindVertexArray(mVertexArray);

    glEnableVertexAttribArray(0);
    mVBO[0].Bind();
    
    //////////////
    // draw top
    //
    if(mVAngle>0.1)
    {
        msg_info() << "draw top!!!!\n";
        msg_info() << "desktop count:" << gRoot->mDesktops.mCount << "\n";
        mat::CMat4d lOldMV=mModelView;
        //transform for desktop 0
        Transform(0,false);
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(0.0,0.0,-gRoot->mDesktops.mDistance)); //move to center

//         mModelView=mat::identity_3d();
//         mProjectionMatrix=mat::identity_3d();
        
        mUniformMV.SetValue(mModelView);
        mUniformMVP.SetValue(mProjectionMatrix*mModelView);
        
        glVertexAttrib3f(1,0.8,0.8,1.0); //color
        glVertexAttrib3f(2,0,1,0);      //normal

        std::vector<float> lVertices;

        double lCovered=gRoot->mDesktops.mAngleCovered*M_PI/180.0;
        for(int i=0;i<gRoot->mDesktops.mCount;i++)
        {
            double lDist=1.0/(2.0*sin(lCovered/2.0));
            double lAngle=gRoot->mDesktops.mAngle[i]*M_PI/180.0-lCovered/2.0;
            lVertices.push_back(lDist*sin(lAngle));
            lVertices.push_back(0.5);
            lVertices.push_back(lDist*cos(lAngle));
        }
        
        mVBO[0].Data(lVertices);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,nullptr);
        
        glDrawArrays(GL_TRIANGLE_FAN,0,gRoot->mDesktops.mCount);
        
        mModelView=lOldMV;

        GLenum err=glGetError();
        if(err)
            msg_info() << "error 2!!!!! " << err << "\n";
    }
    //////////////
    // draw bottom
    //
    else if(mVAngle<-0.1)
    {
        mat::CMat4d lOldMV=mModelView;
        //transform for desktop 0
        Transform(0,false);
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(0.0,0.0,-gRoot->mDesktops.mDistance)); //move to center

        mUniformMV.SetValue(mModelView);
        mUniformMVP.SetValue(mProjectionMatrix*mModelView);
        
        glVertexAttrib3f(1,1.0,0.8,0.8); //color
        glVertexAttrib3f(2,0,-1,0);      //normal

        std::vector<float> lVertices;

        double lCovered=gRoot->mDesktops.mAngleCovered*M_PI/180.0;
        for(int i=gRoot->mDesktops.mCount-1;i>=0;i--)
        {
            double lDist=1.0/(2.0*sin(lCovered/2.0));
            double lAngle=gRoot->mDesktops.mAngle[i]*M_PI/180.0-lCovered/2.0;
            lVertices.push_back(lDist*sin(lAngle));
            lVertices.push_back(-0.5);
            lVertices.push_back(lDist*cos(lAngle));
            glVertex3f(lDist*sin(lAngle),-0.5,lDist*cos(lAngle));
        }

        glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,lVertices.data());
        glEnableVertexAttribArray(0);
        
        glDrawArrays(GL_TRIANGLE_FAN,0,gRoot->mDesktops.mCount);
        
        mModelView=lOldMV;
    }

    mVBO[0].Bind();
    
    mTexProgram.Use();

    ///////////////////////////
    // draw all the desktops
    //
    bool lDrawDock=false;

    for(int d=0;d<gRoot->mDesktops.mCount;d++)
    {
        mat::CMat4d lOldMV=mModelView;

        gRoot->mDesktops.mVisible[d]=
            (cos((gRoot->mDesktops.mAngle[d]-mHAngle)*M_PI/180)>0.01);
        if(!gRoot->mDesktops.mVisible[d])
            continue;

        //transform for current desktop
        Transform(d,true);

        mUniformMV.SetValue(mModelView);
        mUniformMVP.SetValue(mProjectionMatrix*mModelView);
        //actually compose the desktop
        {
            msg_info() << "really drawing desktop " << d << "\n";
            std::vector<float> lVertices;
            
            lVertices.push_back(0);
            lVertices.push_back(0);
            lVertices.push_back(0);
            
            lVertices.push_back(500);
            lVertices.push_back(0);
            lVertices.push_back(0);

            lVertices.push_back(500);
            lVertices.push_back(500);
            lVertices.push_back(0);

            mVBO[0].Data(lVertices);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,nullptr);
            
            glDrawArrays(GL_TRIANGLES,0,3);
        }

        gRoot->GetDesktopRenderer()->Render(d,CDesktopRenderer::MODE_NODOCK);

        mModelView=lOldMV;

        if(!lDrawDock)
        {
            lDrawDock=true;

            for(int i=0;i<gRoot->mClientsStack.size();i++)
            {
                if(!gRoot->mClientsStack[i]->IsOnDesktop(d)) continue;

                if(gRoot->mClientsStack[i]->GetState()&CClient::EState::FULLSCREEN)
                {
                    lDrawDock=false;
                    break;
                }
            }
        }
    }

    if(lDrawDock)
    {
        mat::CMat4d lOldMV=mModelView;
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(mXTranslate,mYTranslate,-mCamDist/mZoom));
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(-0.5,-0.5,0.0));
        mModelView=mModelView*mat::scaling_3d(mat::CVec3d(1.0/mSize.x,-1.0/mSize.y,1.0/mSize.y));
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(0.0,-mSize.y,0.0));

        mUniformMV.SetValue(mModelView);
        mUniformMVP.SetValue(mProjectionMatrix*mModelView);
        gRoot->GetDesktopRenderer()->Render(0,CDesktopRenderer::MODE_DOCKONLY);
        mModelView=lOldMV;
    }

    if(0)//gRoot->mShowStatistics)
    {
        mat::CMat4d lOldMV=mModelView;
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(mXTranslate,mYTranslate,-mCamDist/mZoom));
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(-0.5,-0.5,0.0));
        mModelView=mModelView*mat::scaling_3d(mat::CVec3d(1.0/mSize.x,-1.0/mSize.y,1.0/mSize.y));
        mModelView=mModelView*mat::translation_3d(mat::CVec3d(0.0,-mSize.y,0.0));

        img::CImage_rgba8 lImage({200,30},img::pixel(0,0,0,0));
        CRect_d lRect(2.5,2.5,197.5,27.5);
        img::CImageGC<img::TFormat_rgba8> lGC(lImage);
        lGC.SetLineWidth(5.0);
        lGC.Path().RoundRect(lRect,7.0);
        lGC.SetSource(img::CColor(0.7,0.8,0.9,0.8));
        lGC.FillPath(true);
        lGC.SetSource(img::CColor(0.2,0.4,0.6,1.0));
        lGC.StrokePath();
        lGC.SetSource(img::CColor(0.1,0.2,0.3,1.0));
        lGC.SetFont(img::CFont::GetStock(L"huge"));
        std::wstring lString=wstr_printf(L"%.2f %.2f %.1f",lFirstTime,lSecondTime,gRoot->GetFPS());
        lGC.DrawText(CPoint_d(100,15),lString,img::ETextPos::CENTER);
        msg_info() << lString << "\n";

        glVertexAttrib4f(1,1,1,1,1);      
        glEnable(GL_BLEND);
        glBlendFuncSeparate(GL_ONE,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ZERO);
        
        mFPSTexture.Create2D(lImage);
                     
        CSize_d lS=gRoot->GetSize();
        float lTexCoords[]={0,0,0,1,1,1,1,0};
        float lPixelCoords[]={float(lS.x)-220,float(lS.y)-50,float(lS.x)-220,float(lS.y)-20,
                              float(lS.x)-20,float(lS.y)-20,float(lS.x)-20,float(lS.y)-50};

        glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,0,lPixelCoords);
        glVertexAttribPointer(3,2,GL_FLOAT,GL_FALSE,0,lTexCoords);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(3);
        
        glDrawArrays(GL_QUADS,0,4);
        
        mModelView=lOldMV;
        glDisable(GL_BLEND);
    }

    glBindVertexArray(0);
    gl::CProgram::UseNone();

    //////////////////////////////
    // move to the front buffer
    //

    GLenum err=glGetError();
    if(err)
        msg_info() << "error!!!!! " << err << "\n";
    
    glFlush();
    lFirstTime=get_timer(true)*1000.0;
    glFinish();
    lSecondTime=get_timer()*1000.0;
    gRoot->WaitForVSync();

//    if(gRoot->GetFlag(COMP_ALLDAMAGE))  //paint all
    {
        gRoot->mGLContext->SwapBuffers();
    }
/*    else    //paint region
    {
        //manually swap buffers
        glEnable(GL_SCISSOR_TEST);
        glDrawBuffer(GL_FRONT);

        Region lR=gRoot->mRegions.mPainted.GetID();

        for(int i=0;i<lR->numRects;i++)
        {
            BoxPtr lB=&lR->rects[i];

            int x=lB->x1;
            int y=(int)mSize.y-lB->y2;

            glBitmap(0,0,0,0,x-mRasterPos.x,y-mRasterPos.y,nullptr);

            mRasterPos.Set(x,y);

            glScissor(x,y,lB->x2-lB->x1,lB->y2-lB->y1);
            glCopyPixels(x,y,lB->x2-lB->x1,lB->y2-lB->y1,GL_COLOR);
        }

        glDrawBuffer(GL_BACK);
        glDisable(GL_SCISSOR_TEST);
        glFlush();
    }
*/
    ///////////////////////
    // restore variables
    //
    gRoot->mRegions.mViewportDamage.clear();
    gRoot->UnsetFlag(COMP_DAMAGE);
}

void CCubeRenderer::OnLoop()
{
    DoRecovery(gRoot->GetTime().mCurrent);
}

/*namespace bwm*/ } /*namespace buola*/ }
